﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Threading;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using RSSFeedReader.Controls;
using RSSFeedReader.Data.Models;
using RSSFeedReader.Data.Utilities;
using RSSFeedReader.Dialogs;
using RSSFeedReader.Resources;
using RSSFeedReader.ViewModels.Messages;

namespace RSSFeedReader.ViewModels
{
    /// <summary>
    /// View Model only exposes a subset of the underlying
    /// model properties.
    /// </summary>
    public class MainViewModel : MyViewModelBase
    {
        public static string ChannelDragDropCompleted = "ChannelDragDropCompleted";

        #region Members

        ChannelViewModel _currentChannel;
        readonly ObservableCollection<ChannelViewModel> _channels;
        static ObservableCollection<ChannelViewModel> _channelCollection;

        DispatcherTimer _timer;
        DateTime _lastUpdated;

        #endregion

        #region Constructor

        /// <summary>
        /// Initialize a new instance of the MainViewModel class.
        /// </summary>
        public MainViewModel()
        {
            _channels = new ObservableCollection<ChannelViewModel>();
            Initialize();
        }

        /// <summary>
        /// Initialize data.
        /// </summary>
        private void Initialize()
        {
            // Hook up events
            ChannelDataSource.Instance.ChannelAdded += OnChannelAdded;
            ChannelDataSource.Instance.ChannelSynchronised += OnChannelSynched;
            ChannelDataSource.Instance.ChannelRemoved += OnChannelRemoved;
            ChannelDataSource.Instance.ChannelFolderAdded += OnChannelFolderAdded;
            ChannelDataSource.Instance.ChannelMovedToFolder += OnChannelMovedToFolder;
            ChannelDataSource.Instance.ChannelMoved += OnChannelMoved;
            Messenger.Default.Register<MyTreeViewDragEventArgs>(this, ChannelDragDropCompleted, OnChannelDragDropCompleted);

            // Listen to unhandled ApplicationError
            Application.Current.DispatcherUnhandledException += OnDispatcherUnhandledException;

            // Load the feeds
            LoadChannels();

            // Set the static channel collection
            _channelCollection = _channels;

            // Init refresh timer
            _timer = new DispatcherTimer();
            _timer.Interval = TimeSpan.FromMinutes(ChannelDataSource.Instance.RefreshPeriod);
            _timer.Tick += OnRefreshTimer;
            _timer.Start();
        }

        /// <summary>
        /// Load the cached channels from the ChannelDataSource, then update.
        /// </summary>
        private void LoadChannels()
        {
            foreach (Channel channel in ChannelDataSource.Instance.Channels)
            {
                // Create the new ChannelViewModel and add to the _channels collection.
                ChannelViewModel channelViewModel = ChannelViewModel.CreateNew(channel);
                InitializeChannelViewModel(channelViewModel);

                // Add to the _channelFolders collection.
                _channels.Add(channelViewModel);
            }

            // Put thread to sleep to allow network connection availability to run
            System.Threading.Thread.Sleep(1000);

            // Update channels
            //SetIsLoadingAll();
            //ChannelDataSource.Instance.SyncAllChannels();
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets an ObservableCollection of type <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        public ObservableCollection<ChannelViewModel> Channels
        {
            get { return _channels; }
        }

        /// <summary>
        /// Gets the currently selected <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance.
        /// </summary>
        public ChannelViewModel CurrentChannel
        {
            get { return _currentChannel; }
            set
            {
                if (_currentChannel == value)
                    return;

                _currentChannel = value;

                // Could be set to null from Synching, if left as null
                // would see the error text in the PostView
                if (_currentChannel != null)
                {
                    if (_currentChannel.SelectedPost == null && _currentChannel.ChannelType == ChannelType.Feed)
                    {
                        _currentChannel.Posts[0].IsSelected = true;
                    }
                }

                RaisePropertyChanged("CurrentChannel");
            }
        }

        /// <summary>
        /// Gets the last time the feeds were updated.
        /// </summary>
        public DateTime LastUpdated
        {
            get { return _lastUpdated; }
        }

        /// <summary>
        /// Gets the timer that handles refreshing of feeds.
        /// </summary>
        public DispatcherTimer Timer
        {
            get { return _timer; }
        }

        /// <summary>
        /// Gets the path for the log file for the application.
        /// </summary>
        public static string LogFile
        {
            get
            {
                string logFile = Path.Combine(Path.Combine(ChannelDataSource.Instance.ApplicationDataLocation, "Log"), "Log.txt");

                if (!File.Exists(logFile))
                {
                    string directoryName = Path.GetDirectoryName(logFile);
                    if (directoryName != null && !Directory.Exists(directoryName))
                    {
                        Directory.CreateDirectory(directoryName);
                    }
                    File.CreateText(logFile);
                }

                return logFile;
            }
        }

        #endregion

        #region Commands

        #region ExitCommand
        private RelayCommand _exitCommand;

        /// <summary>
        /// Gets the <see cref="ExitCommand"/>.
        /// </summary>
        public RelayCommand ExitCommand
        {
            get
            {
                return _exitCommand ?? (_exitCommand = new RelayCommand(CloseView));
            }
        }
        #endregion

        #region AddNewFeedCommand
        private RelayCommand _addNewFeedCommand;

        /// <summary>
        /// Gets the <see cref="AddNewFeedCommand"/>.
        /// </summary>
        public RelayCommand AddNewFeedCommand
        {
            get
            {
                return _addNewFeedCommand ?? (_addNewFeedCommand = new RelayCommand(
                    () => Messenger.Default.Send(new ShowWindowMessage<AddNewFeedViewModel>(new AddNewFeedViewModel())),
                    () => ChannelDataSource.Instance.IsNetworkAvailable));
            }
        }
        #endregion

        #region OpenInBrowserCommand
        private RelayCommand _openInBrowserCommand;

        /// <summary>
        /// Gets the <see cref="OpenInBrowserCommand"/>.
        /// </summary>
        public RelayCommand OpenInBrowserCommand
        {
            get
            {
                return _openInBrowserCommand ?? (_openInBrowserCommand = 
                    new RelayCommand(() => Process.Start(CurrentChannel.SelectedPost.Link)));
            }
        }
        #endregion

        #region SyncChannelCommand
        private RelayCommand _syncChannelCommand;

        /// <summary>
        /// Gets the <see cref="SyncChannelCommand"/>.
        /// </summary>
        public RelayCommand SyncChannelCommand
        {
            get
            {
                return _syncChannelCommand ?? (_syncChannelCommand = new RelayCommand(
                    () =>
                    {
                        CurrentChannel.IsLoading = true;
                        ChannelDataSource.Instance.SyncChannel(CurrentChannel.ChannelID);
                    }
                    ,
                    () =>
                    {
                        if (CurrentChannel != null)
                        {
                            return CurrentChannel.ChannelType == ChannelType.Feed &&
                                ChannelDataSource.Instance.IsNetworkAvailable;
                        }
                        return false;
                    }));
            }
        }
        #endregion

        #region SyncAllChannelsCommand
        private RelayCommand _syncAllChannelsCommand;

        /// <summary>
        /// Gets the <see cref="SyncAllChannelsCommand"/>.
        /// </summary>
        public RelayCommand SyncAllChannelsCommand
        {
            get
            {
                return _syncAllChannelsCommand ?? (_syncAllChannelsCommand = new RelayCommand(
                    () =>
                    {
                        SetIsLoadingAll();
                        ChannelDataSource.Instance.SyncAllChannels();
                    }
                    ,
                    () => HasChannels() && ChannelDataSource.Instance.IsNetworkAvailable));
            }
        }
        #endregion

        #region ImportFeedsCommand
        private RelayCommand _importFeedsCommand;

        /// <summary>
        /// Gets the <see cref="ImportFeedsCommand"/>.
        /// </summary>
        public RelayCommand ImportFeedsCommand
        {
            get { return _importFeedsCommand ?? (_importFeedsCommand = new RelayCommand(OnImportFeeds)); }
        }

        private void OnImportFeeds()
        {
            var dialog = new OpenFileDialogMessage(Strings.MainView_ImportFeedsDialogTitle, Strings.OpmlFiles + "|" + Strings.OpmlExtension, ProcessOpenFileDialog);
            Messenger.Default.Send(dialog);
        }

        private void ProcessOpenFileDialog(bool? dialogResult, string fileName)
        {
            if (dialogResult.HasValue && dialogResult.Value)
            {
                try
                {
                    ImportExport.Import(fileName);
                    ShowDialogMessage(Strings.MainView_ImportFeedsSuccessful, Strings.ApplicationName, MessageBoxButton.OK, MessageBoxImage.Information);
                }
                catch (ImportExportException ex)
                {
                    var msg = string.Format(Strings.MainView_ImportFeedsError, ex.Message);
                    if (ex.InnerException != null)
                    {
                        msg += "\tInnerException:" + ex.InnerException.Message;
                    }

                    ShowDialogMessage(msg, Strings.ApplicationName, MessageBoxButton.OK, MessageBoxImage.Error);
                    Logger.LogMessage(Strings.LogTypeFatalError, msg);
                }
            }
        }
        #endregion

        #region ExportFeedsCommand
        private RelayCommand _exportFeedsCommand;

        /// <summary>
        /// Gets the <see cref="ExportFeedsCommand"/>.
        /// </summary>
        public RelayCommand ExportFeedsCommand
        {
            get { return _exportFeedsCommand ?? (_exportFeedsCommand = new RelayCommand(OnExportFeeds, HasChannels)); }
        }

        private void OnExportFeeds()
        {
            var dialog = new SaveFileDialogMessage(Strings.MainView_ExportFeedsDialogTitle, Strings.OpmlFiles + "|" + Strings.OpmlExtension, ProcessSaveFileDialog);
            Messenger.Default.Send(dialog);
        }

        private void ProcessSaveFileDialog(bool? dialogResult, string fileName)
        {
            if (dialogResult.HasValue && dialogResult.Value)
            {
                try
                {
                    ImportExport.Export(fileName);
                    ShowDialogMessage(Strings.MainView_ExportFeedsSuccessful, Strings.ApplicationName, MessageBoxButton.OK, MessageBoxImage.Information);
                }
                catch (ImportExportException ex)
                {
                    string msg = string.Format(Strings.MainView_ExportFeedsError, ex.Message);
                    if (ex.InnerException != null)
                    {
                        msg += "\tInnerException:" + ex.InnerException.Message;
                    }

                    ShowDialogMessage(msg, Strings.ApplicationName, MessageBoxButton.OK, MessageBoxImage.Error);
                    Logger.LogMessage(Strings.LogTypeFatalError, msg);
                }
            }
        }
        #endregion

        #region DeleteChannelCommand
        private RelayCommand _deleteChannelCommand;

        /// <summary>
        /// Gets the <see cref="DeleteChannelCommand"/>.
        /// </summary>
        public RelayCommand DeleteChannelCommand
        {
            get
            {
                return _deleteChannelCommand ?? (_deleteChannelCommand = new RelayCommand(
                    () =>
                    {
                        string message;
                        message = string.Format(
                            CurrentChannel.ChannelType == ChannelType.Feed ? 
                            Strings.DeleteFeedConfirmation : 
                            Strings.DeleteFolderConfirmation,   
                            CurrentChannel.Title);

                        ShowDialogMessage(message,
                                          Strings.ApplicationName,
                                          MessageBoxButton.YesNo,
                                          MessageBoxImage.Warning,
                                          dialogResult =>
                                          {
                                              if (dialogResult == MessageBoxResult.Yes)
                                              {
                                                  var parent = CurrentChannel.Parent;
                                                  ChannelDataSource.Instance.RemoveChannel(CurrentChannel.ChannelID);
                                                  if (parent != null && parent.ChannelType == ChannelType.Folder)
                                                  {
                                                      parent.UpdateFolderNewPosts();
                                                  }
                                                  CurrentChannel = null;
                                              }
                                          });
                    },() => HasChannels() && CurrentChannel != null));
            }
        }
        #endregion

        #region DeleteAllChannelsCommand
        private RelayCommand _deleteAllChannelsCommand;

        /// <summary>
        /// Gets the <see cref="DeleteAllChannelsCommand"/>.
        /// </summary>
        public RelayCommand DeleteAllChannelsCommand
        {
            get
            {
                return _deleteAllChannelsCommand ?? (_deleteAllChannelsCommand = new RelayCommand(
                    () => ShowDialogMessage(
                        Strings.MainViewModel_DeleteAll,
                        Strings.ApplicationName,
                        MessageBoxButton.YesNo,
                        MessageBoxImage.Warning,
                        dialogResult =>
                        {
                            if (dialogResult == MessageBoxResult.Yes)
                            {
                                Channels.Clear();
                                CurrentChannel = null;
                                ChannelDataSource.Instance.RemoveAllChannels();
                                Application.Current.MainWindow.Title = Strings.ApplicationName;
                            }
                        }), HasChannels));
            }
        }
        #endregion

        #region CopyLinkCommand
        private RelayCommand _copyLinkCommand;

        /// <summary>
        /// Gets the <see cref="CopyLinkCommand"/>.
        /// </summary>
        public RelayCommand CopyLinkCommand
        {
            get
            {
                return _copyLinkCommand ?? (_copyLinkCommand = new RelayCommand(
                    () => Clipboard.SetText(CurrentChannel.SelectedPost.Link)));
            }
        }
        #endregion

        #region ManageFeedsCommand
        private RelayCommand _manageFeedsCommand;

        /// <summary>
        /// Gets the <see cref="ManageFeedsCommand"/>.
        /// </summary>
        public RelayCommand ManageFeedsCommand
        {
            get
            {
                return _manageFeedsCommand ?? (_manageFeedsCommand = new RelayCommand(
                    () => Messenger.Default.Send(new ShowWindowMessage<ManageFeedsViewModel>(new ManageFeedsViewModel()))));
            }
        }
        #endregion

        #endregion

        #region ViewErrorLogCommand
        private RelayCommand _viewErrorLogCommand;

        /// <summary>
        /// Gets the <see cref="ViewErrorLogCommand"/>.
        /// </summary>
        public RelayCommand ViewErrorLogCommand
        {
            get
            {
                return _viewErrorLogCommand ?? (_viewErrorLogCommand = new RelayCommand(
                    () => Process.Start(LogFile)));
            }
        }
        #endregion

        #region Internal Properties
        /// <summary>
        /// Gets an ObservableCollection of type <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        internal static ObservableCollection<ChannelViewModel> ChannelCollection
        {
            get { return _channelCollection; }
        }
        #endregion

        #region Private Methods

        /// <summary>
        /// Finds a <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> in the local collection based on the ID.
        /// </summary>
        /// <param name="id">
        /// The id to look for in the Collection of ChannelViewModel.
        /// </param>
        /// <returns>
        /// A <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance if found, null otherwise.
        /// </returns>
        ChannelViewModel FindChannelViewModelById(Guid id)
        {
            foreach (ChannelViewModel channelViewModel in Channels)
            {
                ChannelViewModel foundChannelVM = FindChannelViewModelById(channelViewModel, id).FirstOrDefault();

                if (foundChannelVM != null)
                    return foundChannelVM;
            }

            Logger.LogMessage(Strings.LogTypeWarning, string.Format("No ChannelViewModel with '{0}' id could be found, why?", id));
            return null;
        }

        /// <summary>
        /// Finds a <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance,
        /// whose ChannelViewModel.ID is equal to the ID passed in.
        /// </summary>
        /// <param name="channelViewModel">
        /// The <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> to test against
        /// the condition.
        /// </param>
        /// <param name="id">
        /// The ID to compare against.
        /// </param>
        /// <returns>
        /// A IEnumerable collection of type <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </returns>
        IEnumerable<ChannelViewModel> FindChannelViewModelById(ChannelViewModel channelViewModel,
            Guid id)
        {
            if (channelViewModel.ChannelID == id)
                yield return channelViewModel;

            foreach (ChannelViewModel child in channelViewModel.Children)
                foreach (ChannelViewModel isSameId in FindChannelViewModelById(child, id))
                    yield return isSameId;
        }

        /// <summary>
        /// Initialize the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        /// <param name="channelViewModel">The <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>
        /// to initialize.</param>
        void InitializeChannelViewModel(ChannelViewModel channelViewModel)
        {
            channelViewModel.PropertyChanged += OnChannelViewModelPropertyChanged;
            foreach (ChannelViewModel channelVM in channelViewModel.Children)
            {
                InitializeChannelViewModel(channelVM);
            }
        }

        /// <summary>
        /// Uninitialize the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        /// <param name="channelViewModel">The <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>
        /// to uninitialize.</param>
        void UnInitializeChannelViewModel(ChannelViewModel channelViewModel)
        {
            channelViewModel.PropertyChanged -= OnChannelViewModelPropertyChanged;
            foreach (ChannelViewModel channelVM in channelViewModel.Children)
            {
                UnInitializeChannelViewModel(channelVM);
            }
        }

        /// <summary>
        /// Determines if this <see cref="RSSFeedReader.ViewModels.MainViewModel"/> instance
        /// contains at least one <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance.
        /// </summary>
        /// <returns>
        /// True if one or more ChannelViewModels are found.
        /// </returns>
        bool HasChannels()
        {
            return Channels.Count > 0 && Channels != null;
        }

        /// <summary>
        /// Determines if the ChannelLoadedEventArgs raised an error.
        /// </summary>
        /// <param name="e">Event arguments describing the event.</param>
        /// <returns>True if an error occured.</returns>
        bool IsErrorOnDownload(ChannelAddedEventArgs e)
        {
            var error = e.Error as FeedLoadException;

            if (error != null)
            {
                ShowDialogMessage(
                    string.Format(Strings.MainViewModel_FeedLoadException, error.Message,
                    error.InnerException.Message),
                    Strings.ApplicationName,
                    MessageBoxButton.OK,
                    MessageBoxImage.Information);

                Logger.LogMessage(Strings.LogTypeWarning,
                    string.Format(Strings.MainViewModel_FeedLoadException, error.Message,
                    error.InnerException.Message));

                return true;
            }
            return false;
        }

        /// <summary>
        /// Determines if the ChannelSynchronisedEventArgs raised an error.
        /// </summary>
        /// <param name="e">Event arguments describing the event.</param>
        /// <returns>True if an error occured.</returns>
        bool IsErrorOnSynchronise(ChannelSynchronisedEventArgs e)
        {
            var error = e.Error as FeedSynchroniseException;

            if (error != null)
            {
                try
                {
                    ChannelViewModel channelViewModel = FindChannelViewModelById(error.ChannelID);
                    channelViewModel.IsLoading = false;
                }
                finally
                {
                    Logger.LogMessage(Strings.LogTypeWarning,
                       string.Format(Strings.MainViewModel_FeedSynchroniseException, error.Message,
                       error.InnerException.Message));
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// Set all the Channels to IsLoading = true.
        /// </summary>
        void SetIsLoadingAll()
        {
            foreach (ChannelViewModel channel in Channels)
            {
                SetIsLoading(channel);
            }
        }

        /// <summary>
        /// Set the specified ChannelViewModel.IsLoading property to true.
        /// </summary>
        /// <param name="channelViewModel">The ChannelViewModel to change the IsLoading property.</param>
        void SetIsLoading(ChannelViewModel channelViewModel)
        {
            if (channelViewModel.Children.Count > 0)
            {
                foreach (ChannelViewModel channelChild in channelViewModel.Children)
                    SetIsLoading(channelChild);
            }
            else
            {
                if (channelViewModel.ChannelType == ChannelType.Folder)
                    return;

                channelViewModel.IsLoading = true;
            }
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Event handler for when the timer period elapses.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnRefreshTimer(object sender, EventArgs e)
        {
            if (Channels.Count == 0)
                return;

            SetIsLoadingAll();
            ChannelDataSource.Instance.SyncAllChannels();
            Logger.LogMessage(Strings.LogTypeInformation, "Refresh feeds called @ " + DateTime.Now.ToString());
        }

        /// <summary>
        /// Event handler for when a channel feed is added.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelAdded(object sender, ChannelAddedEventArgs e)
        {
            if (IsErrorOnDownload(e))
                return;

            // Create the new ChannelViewModel and add to the _channelFolders collection.
            var channelViewModel = ChannelViewModel.CreateNew(e.NewChannel);
            InitializeChannelViewModel(channelViewModel);

            // Add to the _channelFolders collection.
            _channels.Add(channelViewModel);
            _channelCollection = _channels;
        }

        /// <summary>
        /// Event handler for when a channel feed is synched.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelSynched(object sender, ChannelSynchronisedEventArgs e)
        {
            ChannelViewModel channelViewModelSynched;
            string previousPostLink = string.Empty;

            if (IsErrorOnSynchronise(e))
                return;

            // Find the ChannelViewModel this will affect, clear the
            // ObservableCollection Posts property and add the
            // results from the sync.
            try
            {
                channelViewModelSynched = FindChannelViewModelById(e.SynchronisedChannel.ChannelID);
            }
            catch (Exception ex)
            {
                Logger.LogMessage(Strings.LogTypeInformation, ex.Message);
                return;
            }

            if (channelViewModelSynched.SelectedPost != null)
            {
                // Keep a record of the previously selected post
                previousPostLink = channelViewModelSynched.SelectedPost.Link;
            }

            channelViewModelSynched.Posts.Clear();

            e.SynchronisedChannel.Posts.ForEach(post => channelViewModelSynched.Posts.Add(new PostViewModel(post)));
            _lastUpdated = DateTime.Now;
            channelViewModelSynched.IsLoading = false;

            // Find the previously selected post in the new list
            // and if found select it
            if (!string.IsNullOrEmpty(previousPostLink))
            {
                try
                {
                    PostViewModel previousPost = channelViewModelSynched.Posts.Single(post => post.Link == previousPostLink);
                    int postIndex = channelViewModelSynched.Posts.IndexOf(previousPost);
                    channelViewModelSynched.Posts[postIndex].IsSelected = true;

                    return;
                }
                catch (Exception ex)
                {
                    Logger.LogMessage(Strings.LogTypeInformation, ex.Message);
                }
            }

            // Only get this far, if the previously selected post could not be found.
            // Set the selected post to null if not the current channel
            // so that we get a true count of new post as the top most post would be selected
            // otherwise thus decrementing the count of new posts by 1
            if (!channelViewModelSynched.Equals(CurrentChannel))
            {
                channelViewModelSynched.SelectedPost = null;
            }
            else
            {
                channelViewModelSynched.Posts[0].IsSelected = true;
            }
        }

        /// <summary>
        /// Event handler for when a channel feed or folder is deleted.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelRemoved(object sender, ChannelRemovedEventArgs e)
        {
            ChannelViewModel channelViewModelToRemove = FindChannelViewModelById(e.OldChannel.ChannelID);

            if (channelViewModelToRemove == null)
                return;

            UnInitializeChannelViewModel(channelViewModelToRemove);

            if (channelViewModelToRemove.Parent != null)
            {
                ChannelViewModel parent =
                    FindChannelViewModelById(channelViewModelToRemove.Parent.ChannelID);
                parent.Children.Remove(channelViewModelToRemove);
            }
            else
            {
                Channels.Remove(channelViewModelToRemove);
            }
        }

        /// <summary>
        /// Event handler for when a channel folder is added.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelFolderAdded(object sender, ChannelFolderAddedEventArgs e)
        {
            if (e.Parent == null)
            {
                var channelViewModel = ChannelViewModel.CreateNew(e.NewFolder);
                InitializeChannelViewModel(channelViewModel);

                _channels.Add(channelViewModel);
                _channelCollection = _channels;
            }
            else
            {
                var channelViewModelParent = FindChannelViewModelById(e.Parent.ChannelID);
                var channelViewModel = ChannelViewModel.CreateNew(e.NewFolder);
                InitializeChannelViewModel(channelViewModel);

                channelViewModelParent.Children.Add(channelViewModel);
                channelViewModel.Parent = channelViewModelParent;
                _channelCollection = _channels;
                channelViewModelParent.IsExpanded = true;
            }
        }

        /// <summary>
        /// Event handler for when a channel or folder is
        /// moved to another folder.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelMovedToFolder(object sender, ChannelMovedToFolderEventArgs e)
        {
            ChannelViewModel folderViewModel = null;
            ChannelViewModel channelViewModelToMove;

            channelViewModelToMove = FindChannelViewModelById(e.ChannelToMove.ChannelID);

            if (e.DestinationFolder != null) // Could be null if moving to root
                folderViewModel = FindChannelViewModelById(e.DestinationFolder.ChannelID);

            if (channelViewModelToMove.Parent != null && folderViewModel != null)
            {
                // Moving from folder to folder
                ChannelViewModel parent = FindChannelViewModelById(channelViewModelToMove.Parent.ChannelID);
                parent.Children.Remove(channelViewModelToMove);
                folderViewModel.Children.Add(channelViewModelToMove);
                channelViewModelToMove.Parent = folderViewModel;
                folderViewModel.IsExpanded = true;
            }
            else if (folderViewModel != null)
            {
                // Coming from root to a folder
                Channels.Remove(channelViewModelToMove);
                folderViewModel.Children.Add(channelViewModelToMove);
                channelViewModelToMove.Parent = folderViewModel;
                folderViewModel.IsExpanded = true;
            }
            else
            {
                // Must be moving back to the root
                if (channelViewModelToMove.Parent != null)
                {
                    ChannelViewModel parent = FindChannelViewModelById(channelViewModelToMove.Parent.ChannelID);
                    parent.Children.Remove(channelViewModelToMove);
                }
                channelViewModelToMove.Parent = null;
                Channels.Add(channelViewModelToMove);
            }

            CurrentChannel = channelViewModelToMove;
            CurrentChannel.IsSelected = true;
        }

        /// <summary>
        /// Event handler for when a property of the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>
        /// instance changes.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            const string isSelected = "IsSelected";
            const string title = "Title";
            ChannelViewModel channelVM = sender as ChannelViewModel;

            if (channelVM != null && (e.PropertyName == isSelected && channelVM.IsSelected))
            {
                CurrentChannel = channelVM;
                Application.Current.MainWindow.Title = string.Format(Strings.MainView_Title_CurrentChannel, CurrentChannel.Title);
            }
            else if (e.PropertyName == title && CurrentChannel == channelVM)
            {
                if (CurrentChannel != null)
                    Application.Current.MainWindow.Title = string.Format(Strings.MainView_Title_CurrentChannel, CurrentChannel.Title);
            }
        }

        /// <summary>
        /// Event handler for DispatcherUnhandledException.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">Event arguments describing the event.</param>
        void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            var error = new StringBuilder();
            error.Append(string.Format("An unhandled application error has occured message: {0}", e.Exception.Message));

            if (e.Exception.InnerException != null)
                error.Append(string.Format("\tInnerException: {0}", e.Exception.InnerException.Message));

            error.Append(string.Format("\tStack trace: {0}", e.Exception.StackTrace));

            ShowDialogMessage(Strings.DispatcherUnhandledExceptionMessage,
            Strings.ApplicationName, MessageBoxButton.OK,
            MessageBoxImage.Warning);

            Logger.LogMessage(Strings.LogTypeFatalError, error.ToString());
            e.Handled = true;
        }

        /// <summary>
        /// Event handler for when a channel is moved.
        /// </summary>
        /// <param name="e">Event arguments describing the event.</param>
        void OnChannelDragDropCompleted(MyTreeViewDragEventArgs e)
        {
            if (e.Destination is ChannelViewModel)
            {
                ChannelDataSource.Instance.MoveChannel(e.Source.ChannelID, ((ChannelViewModel)e.Destination).ChannelID);
            }
            else if (e.Destination is MainViewModel || e.Destination is ManageFeedsViewModel)
            {
                ChannelDataSource.Instance.MoveChannel(e.Source.ChannelID, Guid.Empty);
            }
        }

        /// <summary>
        /// Event handler for when a channel is moved.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">Event arguments describing the event.</param>
        void OnChannelMoved(object sender, ChannelMovedEventArgs e)
        {
            ChannelViewModel channelToMove;
            ChannelViewModel channelToMoveParent;
            ChannelViewModel channelToMoveTo;
            ChannelViewModel channelToMoveToParent;
            int channelToMoveToIndex = -1;

            channelToMove = Common.FindChannelViewModelById(e.ChannelToMove.ChannelID, _channels.ToList());
            channelToMoveParent =
                channelToMove.Parent == null ? null : Common.FindChannelViewModelById(channelToMove.Parent.ChannelID, _channels.ToList());

            if (e.ChannelToMoveTo == null)
            {
                // Must be moving back to the root
                if (channelToMoveParent != null)
                    channelToMoveParent.Children.Remove(channelToMove);
                channelToMove.Parent = null;
                _channels.Add(channelToMove);
                CurrentChannel = channelToMove;
                return;
            }

            channelToMoveTo = Common.FindChannelViewModelById(e.ChannelToMoveTo.ChannelID, _channels.ToList());
            channelToMoveToParent =
                channelToMoveTo.Parent == null ? null : Common.FindChannelViewModelById(channelToMoveTo.Parent.ChannelID, _channels.ToList());

            if (channelToMoveParent != null && channelToMoveToParent != null)
            {
                channelToMoveToIndex = channelToMoveToParent.Children.IndexOf(channelToMoveTo);
                if (channelToMoveParent == channelToMoveToParent)
                {
                    // Moving from within the same folder
                    if (channelToMoveTo == channelToMoveToParent.Children.Last())
                    {
                        channelToMoveParent.Children.Remove(channelToMove);
                        channelToMoveToParent.Children.Add(channelToMove);
                    }
                    else if (channelToMoveTo == channelToMoveToParent.Children.First())
                    {
                        channelToMoveParent.Children.Remove(channelToMove);
                        channelToMoveToParent.Children.Insert(0, channelToMove);
                    }
                    else
                    {
                        channelToMoveParent.Children.Remove(channelToMove);
                        channelToMoveToParent.Children.Insert(channelToMoveToIndex, channelToMove);
                    }
                    channelToMove.Parent = channelToMoveToParent;
                }
                else
                {
                    // Moving from another folder
                    channelToMoveParent.Children.Remove(channelToMove);

                    if (channelToMoveTo == channelToMoveToParent.Children.Last())
                    {
                        channelToMoveToParent.Children.Add(channelToMove);
                    }
                    else if (channelToMoveTo == channelToMoveToParent.Children.First())
                    {
                        channelToMoveToParent.Children.Insert(0, channelToMove);
                    }
                    else
                    {
                        channelToMoveToParent.Children.Insert(channelToMoveToIndex, channelToMove);
                    }
                    channelToMove.Parent = channelToMoveToParent;
                }
            }
            else if (channelToMoveParent == null && channelToMoveToParent != null)
            {
                // Coming from root to a folder
                channelToMoveToIndex = channelToMoveToParent.Children.IndexOf(channelToMoveTo);
                _channels.Remove(channelToMove);

                if (channelToMoveTo == channelToMoveToParent.Children.Last())
                {
                    channelToMoveToParent.Children.Add(channelToMove);
                }
                else if (channelToMoveTo == channelToMoveToParent.Children.First())
                {
                    channelToMoveToParent.Children.Insert(0, channelToMove);
                }
                else
                {
                    channelToMoveToParent.Children.Insert(channelToMoveToIndex + 1, channelToMove);
                }

                channelToMove.Parent = channelToMoveToParent;
            }
            else if (channelToMoveParent != null)
            {
                // Coming from folder to root at a specified feed
                channelToMoveToIndex = _channels.IndexOf(channelToMoveTo);
                channelToMoveParent.Children.Remove(channelToMove);

                if (channelToMoveTo == _channels.Last())
                {
                    _channels.Add(channelToMove);
                }
                else if (channelToMoveTo == _channels.First())
                {
                    _channels.Insert(0, channelToMove);
                }
                else
                {
                    _channels.Insert(channelToMoveToIndex + 1, channelToMove);
                }

                channelToMove.Parent = null;
            }
            else
            {
                channelToMoveToIndex = _channels.IndexOf(channelToMoveTo);

                // Moving from within the same folder
                if (channelToMoveTo == _channels.Last())
                {
                    _channels.Remove(channelToMove);
                    _channels.Add(channelToMove);
                }
                else if (channelToMoveTo == _channels.First())
                {
                    _channels.Remove(channelToMove);
                    _channels.Insert(0, channelToMove);
                }
                else
                {
                    _channels.Remove(channelToMove);
                    _channels.Insert(channelToMoveToIndex, channelToMove);
                }
                channelToMove.Parent = null;
            }

            CurrentChannel = channelToMove;
        }

        #endregion
    }
}
